package sunyu.tools.common;

import jodd.datetime.JDateTime;
import org.nutz.lang.Times;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * 日期时间工具类
 *
 * @author 孙宇
 */
@Component
public class TimeTools {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 计算两个日期相差多少天
     *
     * @param d1
     * @param d2
     * @return
     */
    public long getDays(Date d1,
                        Date d2) {
        long l = getHour(d1, d2);
        return l / 24;
    }

    /**
     * 计算两个日期相差多少小时
     *
     * @param d1
     * @param d2
     * @return
     */
    public long getHour(Date d1,
                        Date d2) {
        long l = getMinute(d1, d2);
        return l / 60;
    }

    /**
     * 计算两个日期相差多少分钟
     *
     * @param d1
     * @param d2
     * @return
     */
    public long getMinute(Date d1,
                          Date d2) {
        long l = getSeconds(d1, d2);
        return l / 60;
    }

    /**
     * 计算两个日期相差多少秒
     *
     * @param d1
     * @param d2
     * @return
     */
    public long getSeconds(Date d1,
                           Date d2) {
        long l = d1.getTime() - d2.getTime();
        return Math.abs(l / 1000);
    }


    /**
     * 添加或减少n秒
     *
     * @param date
     * @param n
     * @return
     */
    public Date addSecond(Date date,
                          int n) {
        JDateTime jdt = new JDateTime(date);
        jdt.addSecond(n);
        return jdt.convertToDate();
    }

    /**
     * 添加或减少n分钟
     *
     * @param date
     * @param n
     * @return
     */
    public Date addMinute(Date date,
                          int n) {
        JDateTime jdt = new JDateTime(date);
        jdt.addMinute(n);
        return jdt.convertToDate();
    }

    /**
     * 添加或减少n小时
     *
     * @param date
     * @param n
     * @return
     */
    public Date addHour(Date date,
                        int n) {
        JDateTime jdt = new JDateTime(date);
        jdt.addHour(n);
        return jdt.convertToDate();
    }

    /**
     * 添加或减少N天
     *
     * @param date
     * @param n    为负数时是减少
     * @return
     */
    public Date addDay(Date date,
                       int n) {
        JDateTime jdt = new JDateTime(date);
        jdt.addDay(n);
        return jdt.convertToDate();
    }

    /**
     * 添加或减少N月
     *
     * @param date
     * @param n    为负数时是减少
     * @return
     */
    public Date addMonth(Date date,
                         int n) {
        JDateTime jdt = new JDateTime(date);
        jdt.addMonth(n);
        return jdt.convertToDate();
    }

    /**
     * 添加或减少n年
     *
     * @param date
     * @param n
     * @return
     */
    public Date addYear(Date date,
                        int n) {
        JDateTime jdt = new JDateTime(date);
        jdt.addYear(n);
        return jdt.convertToDate();
    }


    /**
     * 判断一年是否为闰年，如果给定年份小于1全部为 false
     *
     * @param year 年份，比如 2012 就是二零一二年
     * @return 给定年份是否是闰年
     */
    public boolean leapYear(int year) {
        return Times.leapYear(year);
    }

    /**
     * 判断某年（不包括自己）之前有多少个闰年
     *
     * @param year 年份，比如 2012 就是二零一二年
     * @return 闰年的个数
     */
    public int countLeapYear(int year) {
        return Times.countLeapYear(year);
    }

    /**
     * 将一个秒数（天中），转换成一个如下格式的数组:
     * <p>
     * <pre>
     * [0-23][0-59[-059]
     * </pre>
     *
     * @param sec 秒数
     * @return 时分秒的数组
     */
    public int[] T(int sec) {
        return Times.T(sec);
    }

    /**
     * 将一个时间字符串，转换成一个一天中的绝对秒数
     *
     * @param ts 时间字符串，符合格式 "HH:mm:ss" 或者 "HH:mm"
     * @return 一天中的绝对秒数
     */
    public int T(String ts) {
        return Times.T(ts);
    }

    /**
     * 返回服务器当前时间
     *
     * @return 服务器当前时间
     */
    public Date now() {
        return Times.now();
    }


    /**
     * 根据默认时区计算时间字符串的绝对毫秒数
     *
     * @param ds 时间字符串
     * @return 绝对毫秒数
     * @see #ams(String, TimeZone)
     */
    public long ams(String ds) {
        return Times.ams(ds);
    }

    /**
     * 根据字符串得到相对于 "UTC 1970-01-01 00:00:00" 的绝对毫秒数。
     * 本函数假想给定的时间字符串是本地时间。所以计算出来结果后，还需要减去时差
     * <p>
     * 支持的时间格式字符串为:
     * <p>
     * <pre>
     * yyyy-MM-dd HH:mm:ss
     * yyyy-MM-dd HH:mm:ss.SSS
     * yy-MM-dd HH:mm:ss;
     * yy-MM-dd HH:mm:ss.SSS;
     * yyyy-MM-dd;
     * yy-MM-dd;
     * HH:mm:ss;
     * HH:mm:ss.SSS;
     * </pre>
     * <p>
     * 时间字符串后面可以跟 +8 或者 +8:00 表示 GMT+8:00 时区。 同理 -9 或者 -9:00 表示 GMT-9:00 时区
     *
     * @param ds 时间字符串
     * @param tz 你给定的时间字符串是属于哪个时区的
     * @return 时间
     */
    public long ams(String ds,
                    TimeZone tz) {
        return Times.ams(ds, tz);
    }


    /**
     * 返回时间对象在一天中的毫秒数
     *
     * @param d 时间对象
     * @return 时间对象在一天中的毫秒数
     */
    public long ms(Date d) {
        return Times.ms(d);
    }

    /**
     * 返回时间对象在一天中的毫秒数
     *
     * @param c 时间对象
     * @return 时间对象在一天中的毫秒数
     */
    public int ms(Calendar c) {
        return Times.ms(c);
    }

    /**
     * 返回当前时间在一天中的毫秒数
     *
     * @return 当前时间在一天中的毫秒数
     */
    public int ms() {
        return Times.ms();
    }

    /**
     * 根据一个当天的绝对毫秒数，得到一个时间字符串，格式为 "HH:mm:ss.EEE"
     *
     * @param ms 当天的绝对毫秒数
     * @return 时间字符串
     */
    public String mss(int ms) {
        return Times.mss(ms);
    }

    /**
     * 根据一个当天的绝对秒数，得到一个时间字符串，格式为 "HH:mm:ss"
     *
     * @return 时间字符串
     */
    public String secs(int sec) {
        return Times.secs(sec);

    }

    /**
     * 返回时间对象在一天中的秒数
     *
     * @param d 时间对象
     * @return 时间对象在一天中的秒数
     */
    public int sec(Date d) {
        return Times.sec(d);
    }

    /**
     * 返回当前时间在一天中的秒数
     *
     * @return 当前时间在一天中的秒数
     */
    public int sec() {
        return Times.sec();
    }

    /**
     * 根据字符串得到时间对象
     *
     * @param ds 时间字符串
     * @return 时间
     * @see #ams(String)
     */
    public Date D(String ds) {
        return Times.D(ds);
    }


    /**
     * 计算一个给定日期，距离 1970 年 1 月 1 日有多少天
     *
     * @param yy 年，比如 1999，或者 43
     * @param MM 月，一月为 1，十二月为 12
     * @param dd 日，每月一号为 1
     * @return 距离 1970 年 1 月 1 日的天数
     */
    public int D1970(int yy,
                     int MM,
                     int dd) {
        return Times.D1970(yy, MM, dd);
    }

    /**
     * 根据毫秒数得到时间
     *
     * @param ms 时间的毫秒数
     * @return 时间
     */
    public Date D(long ms) {
        return Times.D(ms);
    }

    /**
     * 根据字符串得到时间
     * <p>
     * <pre>
     * 如果你输入了格式为 "yyyy-MM-dd HH:mm:ss"
     *    那么会匹配到秒
     *
     * 如果你输入格式为 "yyyy-MM-dd"
     *    相当于你输入了 "yyyy-MM-dd 00:00:00"
     * </pre>
     *
     * @param ds 时间字符串
     * @return 时间
     */
    public Calendar C(String ds) {
        return Times.C(ds);
    }

    /**
     * 根据日期对象得到时间
     *
     * @param d 时间对象
     * @return 时间
     */
    public Calendar C(Date d) {
        return Times.C(d);
    }

    /**
     * 根据毫秒数得到时间
     *
     * @param ms 时间的毫秒数
     * @return 时间
     */
    public Calendar C(long ms) {
        return Times.C(ms);
    }

    /**
     * 把时间转换成格式为 y-M-d H:m:s.S 的字符串
     *
     * @param d 时间对象
     * @return 该时间的字符串形式 , 格式为 y-M-d H:m:s.S
     */
    public String sDTms(Date d) {
        return Times.sDTms(d);
    }

    /**
     * 把时间转换成格式为 yy-MM-dd HH:mm:ss.SSS 的字符串
     *
     * @param d 时间对象
     * @return 该时间的字符串形式 , 格式为 yy-MM-dd HH:mm:ss.SSS
     */
    public String sDTms2(Date d) {
        return Times.sDTms2(d);
    }

    /**
     * 把时间转换成格式为 yyyy-MM-dd HH:mm:ss 的字符串
     *
     * @param d 时间对象
     * @return 该时间的字符串形式 , 格式为 yyyy-MM-dd HH:mm:ss
     */
    public String sDT(Date d) {
        return Times.sDT(d);
    }

    /**
     * 把时间转换成格式为 yyyy-MM-dd 的字符串
     *
     * @param d 时间对象
     * @return 该时间的字符串形式 , 格式为 yyyy-MM-dd
     */
    public String sD(Date d) {
        return Times.sD(d);
    }

    /**
     * 将一个秒数（天中），转换成一个格式为 HH:mm:ss 的字符串
     *
     * @param sec 秒数
     * @return 格式为 HH:mm:ss 的字符串
     */
    public String sT(int sec) {
        return Times.sT(sec);
    }

    /**
     * 将一个秒数（天中），转换成一个格式为 HH:mm 的字符串（精确到分钟）
     *
     * @param sec 秒数
     * @return 格式为 HH:mm:ss 的字符串
     */
    public String sTmin(int sec) {
        return Times.sTmin(sec);
    }

    /**
     * 以本周为基础获得某一周的时间范围
     *
     * @param off 从本周偏移几周， 0 表示本周，-1 表示上一周，1 表示下一周
     * @return 时间范围(毫秒级别)
     */
    public Date[] week(int off) {
        return Times.week(off);
    }

    /**
     * 以某周为基础获得某一周的时间范围
     *
     * @param base 基础时间，毫秒
     * @param off  从本周偏移几周， 0 表示本周，-1 表示上一周，1 表示下一周
     * @return 时间范围(毫秒级别)
     */
    public Date[] week(long base,
                       int off) {
        return Times.week(base, off);
    }

    /**
     * 以本周为基础获得时间范围
     *
     * @param offL 从本周偏移几周， 0 表示本周，-1 表示上一周，1 表示下一周
     * @param offR 从本周偏移几周， 0 表示本周，-1 表示上一周，1 表示下一周
     * @return 时间范围(毫秒级别)
     */
    public Date[] weeks(int offL,
                        int offR) {
        return Times.weeks(offL, offR);
    }

    /**
     * 按周获得某几周周一 00:00:00 到周六 的时间范围
     * <p>
     * 它会根据给定的 offL 和 offR 得到一个时间范围
     * <p>
     * 对本函数来说 week(-3,-5) 和 week(-5,-3) 是一个意思
     *
     * @param base 基础时间，毫秒
     * @param offL 从本周偏移几周， 0 表示本周，-1 表示上一周，1 表示下一周
     * @param offR 从本周偏移几周， 0 表示本周，-1 表示上一周，1 表示下一周
     * @return 时间范围(毫秒级别)
     */
    public Date[] weeks(long base,
                        int offL,
                        int offR) {
        return Times.weeks(base, offL, offR);
    }


    /**
     * 将一个时间格式化成容易被人类阅读的格式
     * <p>
     * <pre>
     * 如果 1 分钟内，打印 Just Now
     * 如果 1 小时内，打印多少分钟
     * 如果 1 天之内，打印多少小时之前
     * 如果是今年之内，打印月份和日期
     * 否则打印月份和年
     * </pre>
     *
     * @param d
     * @return
     */
    public String formatForRead(Date d) {
        return Times.formatForRead(d);
    }

    /**
     * 以给定的时间格式来安全的对时间进行格式化，并返回格式化后所对应的字符串
     *
     * @param fmt 时间格式
     * @param d   时间对象
     * @return 格式化后的字符串
     */
    public String format(DateFormat fmt,
                         Date d) {
        return Times.format(fmt, d);
    }

    /**
     * 以给定的时间格式来安全的对时间进行格式化，并返回格式化后所对应的字符串
     *
     * @param fmt 时间格式
     * @param d   时间对象
     * @return 格式化后的字符串
     */
    public String format(String fmt,
                         Date d) {
        if (d == null) {
            return null;
        }
        return Times.format(fmt, d);
    }

    /**
     * 以给定的时间格式来安全的解析时间字符串，并返回解析后所对应的时间对象（包裹RuntimeException）
     *
     * @param fmt 时间格式
     * @param s   时间字符串
     * @return 该时间字符串对应的时间对象
     */
    public Date parseq(DateFormat fmt,
                       String s) {
        return Times.parseq(fmt, s);
    }

    /**
     * 以给定的时间格式来安全的解析时间字符串，并返回解析后所对应的时间对象（包裹RuntimeException）
     *
     * @param fmt 时间格式
     * @param s   时间字符串
     * @return 该时间字符串对应的时间对象
     */
    public Date parseq(String fmt,
                       String s) {
        return Times.parseq(fmt, s);
    }

    /**
     * 以给定的时间格式来安全的解析时间字符串，并返回解析后所对应的时间对象
     *
     * @param fmt 时间格式
     * @param s   日期时间字符串
     * @return 该时间字符串对应的时间对象
     */
    public Date parse(DateFormat fmt,
                      String s) throws ParseException {
        return Times.parse(fmt, s);
    }

    /**
     * 以给定的时间格式来安全的解析时间字符串，并返回解析后所对应的时间对象
     *
     * @param fmt 时间格式
     * @param s   日期时间字符串
     * @return 该时间字符串对应的时间对象
     */
    public Date parse(String fmt,
                      String s) throws ParseException {
        return Times.parse(fmt, s);
    }


    /**
     * 方便的把时间换算成毫秒数
     * <p>
     * 支持几个单位, s(秒), m(分钟), h(小时), d(天)
     * <p>
     * 比如:
     * <p>
     * 100s -> 100000 <br>
     * 2m -> 120000 <br>
     * 3h -> 10800000 <br>
     *
     * @param tstr
     * @return
     */
    public long toMillis(String tstr) {
        return Times.toMillis(tstr);
    }


    /**
     * 一段时间长度的毫秒数转换为一个时间长度的字符串
     * <p>
     * 1000 -> 1S
     * <p>
     * 120000 - 2M
     *
     * @param mi 毫秒数
     * @return 可读的文字
     */
    public String fromMillis(long mi) {
        return Times.fromMillis(mi);
    }

    /**
     * fromMillis的中文版本
     * <p>
     * 1000 -> 1秒
     * <p>
     * 120000 - 2分
     *
     * @param mi 毫秒数
     * @return 可读的文字
     */
    public String fromMillisCN(long mi) {
        return Times.fromMillisCN(mi);
    }

}
